#include "EstrategiaHashSHA1.h"

namespace Toolkit
{

EstrategiaHashSHA1::EstrategiaHashSHA1()
{
}

EstrategiaHashSHA1::~EstrategiaHashSHA1()
{
}

string EstrategiaHashSHA1::hash(const string &claveSerializada) const
{
    bool hexa = false;
    return calcularSha1(claveSerializada, hexa);
}

int EstrategiaHashSHA1::leftrotate(unsigned int valor, unsigned int shift) const
{
    shift &= 31;
    return (valor << shift) | (valor >> (32 - shift));
}

void EstrategiaHashSHA1::appendIntToStr(string& palabra, unsigned int entero) const
{

    unsigned int ent = htonl(entero);
    char aux[4];
    memcpy(aux, &ent, 4);
    palabra.append(aux, 4);
}

void EstrategiaHashSHA1::appendIntToStr(string& palabra,
        unsigned long int entero) const
{
    unsigned long int ent = entero;
    char aux[4];
    memcpy(aux, &ent, 4);
    for (int i = 3; i >= 0; i--)
    {
        palabra += aux[i];
    }
}

unsigned char toUnsignedChar(char arg)
{
    unsigned aux = arg;
    if (arg < 0)
    {
        return aux + 256;
    }
    else
    {
        return arg;
    }
}

int EstrategiaHashSHA1::strToInt(const string& palabra) const
{
    return toUnsignedChar(palabra[3]) + toUnsignedChar(palabra[2]) * 256
            + toUnsignedChar(palabra[1]) * 65536 + toUnsignedChar(palabra[0])
            * 16777216;
}

string EstrategiaHashSHA1::intToHexaStr(int arg) const
{
    char aux[33];

    sprintf(aux, "%x", arg);
    aux[32] = '\0';
    string resultado = aux;

    return resultado;
}

string EstrategiaHashSHA1::intToMemStr(int arg) const
{
    int ent = htonl(arg);
    char aux[5];

    memcpy(aux, &ent, 4);
    aux[4] = '\0';
    string aux2;

    aux2.append(aux, 4);

    return aux2;

}

string EstrategiaHashSHA1::calcularSha1(const string& entrada, bool hexa) const
{
    string mensaje = entrada;

    // inicializo los valores de hash. Htonl convierte a big endian

    unsigned int h0 = 0x67452301;

    unsigned int h1 = 0xEFCDAB89;

    unsigned int h2 = 0x98BADCFE;

    unsigned int h3 = 0x10325476;

    unsigned int h4 = 0xC3D2E1F0;

    unsigned long int tamanioMensaje = mensaje.size() * 8;

    // creo 8 bits con un uno y el resto ceros

    unsigned char aux1 = 0x80;

    unsigned char byteNulo = '\0';

    mensaje += aux1;

    while ((mensaje.size() + 4) % 64 != 0)
    {

        mensaje += byteNulo;
    }

    appendIntToStr(mensaje, tamanioMensaje);

    // divido el mensaje en pedazos de 64 bytes(512 bits)

    unsigned int cantChunks = mensaje.size() / 64;

    for (unsigned int iChunk = 0; iChunk < cantChunks; iChunk++)
    {
        // selecciono el chunk actual

        string chunk = mensaje.substr(iChunk * 64, 64);

        // creo 80 papabras w de 4 bytes(32 bits) cada una

        unsigned int w[80];

        // copio la memoria de a cuatro chars y los meto en ints

        for (unsigned int i = 0; i < 16; i++)
        {

            w[i] = strToInt(chunk.substr(i * 4, 4));

        }

        // creo el resto del vector

        for (unsigned int i = 16; i < 80; i++)
        {

            w[i] = leftrotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);

        }

        unsigned int a = h0;

        unsigned int b = h1;

        unsigned int c = h2;

        unsigned int d = h3;

        unsigned int e = h4;

        unsigned int f;

        unsigned int k;

        // loop principal

        for (unsigned int i = 0; i < 80; i++)
        {

            if (i < 20)
            {

                f = (b & c) | ((~b) & d);

                k = 0x5A827999;

            }
            else if (i < 40)
            {

                f = b ^ c ^ d;

                k = 0x6ED9EBA1;

            }
            else if (i < 60)
            {

                f = (b & c) | (b & d) | (c & d);

                k = 0x8F1BBCDC;

            }
            else if (i < 80)
            {

                f = b ^ c ^ d;

                k = 0xCA62C1D6;

            }

            unsigned int temp = leftrotate(a, 5) + f + e + k + w[i];

            e = d;

            d = c;

            c = leftrotate(b, 30);

            b = a;

            a = temp;
        }

        // actualizo los h

        h0 = h0 + a;

        h1 = h1 + b;

        h2 = h2 + c;

        h3 = h3 + d;

        h4 = h4 + e;

    }

    string resultado;

    if (hexa)
    {

        // imprimio los h en hexa y los meto en un string

        resultado += intToHexaStr(h0);

        resultado += intToHexaStr(h1);

        resultado += intToHexaStr(h2);

        resultado += intToHexaStr(h3);

        resultado += intToHexaStr(h4);

    }
    else
    {
        // copio la memoria de los ints y la meto en chars

        resultado += intToMemStr(h0);

        resultado += intToMemStr(h1);

        resultado += intToMemStr(h2);

        resultado += intToMemStr(h3);

        resultado += intToMemStr(h4);

    }

    return resultado;

}

}
